#include <stdio.h>
#include <stdlib.h>
#include <gmp.h>
#include <sys/time.h>

#define NUMDIGITOS 10000001
#define NUMITERACOES 100
#define PRECISAO_NUMERO_ALEATORIO 1000
#define NOMEARQUIVO "monte_carlo_sequencial.txt"
#define LOG_2_10 3.32192809488736234787


// Estrutura para o timer
struct timeval timerInicial, timerFinal;

int main(void)
{
    // Timer inicial
    gettimeofday(&timerInicial, NULL);

    // Variáveis de iteração
    int pontosTotal;

    // Variáveis do método
    mpf_t x;
    mpf_t y;
    mpf_t z;

    mpf_t pontosDentro;

    mpf_t pi;

    gmp_randstate_t numeroAleatorio;

    // Precisão
    mpf_set_default_prec(LOG_2_10 * NUMDIGITOS);

    // Inicializa as variáveis
    mpf_inits(x, y, z, NULL);
    mpf_init(pontosDentro);
    mpf_init(pi);

    gmp_randinit_default(numeroAleatorio);

    // Arquivo de saída
    FILE *arqSaida;
    arqSaida = fopen(NOMEARQUIVO, "w");

    // ERRO: Arquivo inexistente
    if(arqSaida == NULL)
        exit(EXIT_FAILURE);

    // MÉTODO DE MONTE CARLO
    for(pontosTotal = 1; pontosTotal < NUMITERACOES; pontosTotal++)
    {
        // X = rand[0,1]
        mpf_urandomb(x, numeroAleatorio, PRECISAO_NUMERO_ALEATORIO);

        // Y = rand[0,1]
        mpf_urandomb(y, numeroAleatorio, PRECISAO_NUMERO_ALEATORIO);

        // X = X^2
        mpf_pow_ui(x, x, 2);

        // Y = Y^2
        mpf_pow_ui(y, y, 2);

        // Z = X + Y
        mpf_add(z, x, y);

        // Se Z < 1
        if(mpf_cmp_d(z, 1.0) < 0.0)
        {
            // P(dentro) = P(dentro) + 1
            mpf_add_ui(pontosDentro, pontosDentro, 1);
        }

        // PI = 4 * P(dentro) / P(total)
        mpf_div_ui(pi, pontosDentro, pontosTotal);
        mpf_mul_ui(pi, pi, 4);

        // Imprime o valor aproximado de PI
        gmp_printf("PI(%d) = %.15Ff\n", pontosTotal, pi);
    }

    // Salva o pi
    mpf_out_str(arqSaida, 10, NUMDIGITOS, pi);

    // Limpa as variáveis
    mpf_clears(x, y, z, NULL);
    mpf_clear(pontosDentro);
    mpf_clear(pi);
    gmp_randclear(numeroAleatorio);

    // Encerra o arquivo
    fclose(arqSaida);

    // Timer final
    gettimeofday(&timerFinal, NULL);

    // Imprime o resumo
    printf("\n\nRESUMO DA EXECUCAO\n\n");

    // Imprime o número de iterações
    printf("Numero de iteracoes: %d\n", NUMITERACOES);

    // Imprime o tempo de execução em microssegundos (us)
    printf("Tempo de execucao: %d us\n", (int) (timerFinal.tv_usec - timerInicial.tv_usec));

    // Retorno
    return EXIT_SUCCESS;
}
